/*!
 * Crypto-JS contribution from Simon Greatrix
 */

(function () {

    var C = (typeof window === 'undefined') ? require('./Crypto').Crypto : window.Crypto;

// Create pad namespace
    var C_pad = C.pad = {};

// Calculate the number of padding bytes required.
    function _requiredPadding(cipher, message) {
        var blockSizeInBytes = cipher._blocksize * 4;
        var reqd = blockSizeInBytes - message.length % blockSizeInBytes;
        return reqd;
    };

// Remove padding when the final byte gives the number of padding bytes.
    var _unpadLength = function (message) {
        var pad = message.pop();
        for (var i = 1; i < pad; i++) {
            message.pop();
        }
    };

// No-operation padding, used for stream ciphers
    C_pad.NoPadding = {
        pad: function (cipher, message) {
        },
        unpad: function (message) {
        }
    };

// Zero Padding.
//
// If the message is not an exact number of blocks, the final block is
// completed with 0x00 bytes. There is no unpadding.
    C_pad.ZeroPadding = {
        pad: function (cipher, message) {
            var blockSizeInBytes = cipher._blocksize * 4;
            var reqd = message.length % blockSizeInBytes;
            if (reqd != 0) {
                for (reqd = blockSizeInBytes - reqd; reqd > 0; reqd--) {
                    message.push(0x00);
                }
            }
        },

        unpad: function (message) {
        }
    };

// ISO/IEC 7816-4 padding.
//
// Pads the plain text with an 0x80 byte followed by as many 0x00
// bytes are required to complete the block.
    C_pad.iso7816 = {
        pad: function (cipher, message) {
            var reqd = _requiredPadding(cipher, message);
            message.push(0x80);
            for (; reqd > 1; reqd--) {
                message.push(0x00);
            }
        },

        unpad: function (message) {
            while (message.pop() != 0x80) {
            }
        }
    };

// ANSI X.923 padding
//
// The final block is padded with zeros except for the last byte of the
// last block which contains the number of padding bytes.
    C_pad.ansix923 = {
        pad: function (cipher, message) {
            var reqd = _requiredPadding(cipher, message);
            for (var i = 1; i < reqd; i++) {
                message.push(0x00);
            }
            message.push(reqd);
        },

        unpad: _unpadLength
    };

// ISO 10126
//
// The final block is padded with random bytes except for the last
// byte of the last block which contains the number of padding bytes.
    C_pad.iso10126 = {
        pad: function (cipher, message) {
            var reqd = _requiredPadding(cipher, message);
            for (var i = 1; i < reqd; i++) {
                message.push(Math.floor(Math.random() * 256));
            }
            message.push(reqd);
        },

        unpad: _unpadLength
    };

// PKCS7 padding
//
// PKCS7 is described in RFC 5652. Padding is in whole bytes. The
// value of each added byte is the number of bytes that are added,
// i.e. N bytes, each of value N are added.
    C_pad.pkcs7 = {
        pad: function (cipher, message) {
            var reqd = _requiredPadding(cipher, message);
            for (var i = 0; i < reqd; i++) {
                message.push(reqd);
            }
        },

        unpad: _unpadLength
    };

// Create mode namespace
    var C_mode = C.mode = {};

    /**
     * Mode base "class".
     */
    var Mode = C_mode.Mode = function (padding) {
        if (padding) {
            this._padding = padding;
        }
    };

    Mode.prototype = {
        encrypt: function (cipher, m, iv) {
            this._padding.pad(cipher, m);
            this._doEncrypt(cipher, m, iv);
        },

        decrypt: function (cipher, m, iv) {
            this._doDecrypt(cipher, m, iv);
            this._padding.unpad(m);
        },

        // Default padding
        _padding: C_pad.iso7816
    };


    /**
     * Electronic Code Book mode.
     *
     * ECB applies the cipher directly against each block of the input.
     *
     * ECB does not require an initialization vector.
     */
    var ECB = C_mode.ECB = function () {
        // Call parent constructor
        Mode.apply(this, arguments);
    };

// Inherit from Mode
    var ECB_prototype = ECB.prototype = new Mode;

// Concrete steps for Mode template
    ECB_prototype._doEncrypt = function (cipher, m, iv) {
        var blockSizeInBytes = cipher._blocksize * 4;
        // Encrypt each block
        for (var offset = 0; offset < m.length; offset += blockSizeInBytes) {
            cipher._encryptblock(m, offset);
        }
    };
    ECB_prototype._doDecrypt = function (cipher, c, iv) {
        var blockSizeInBytes = cipher._blocksize * 4;
        // Decrypt each block
        for (var offset = 0; offset < c.length; offset += blockSizeInBytes) {
            cipher._decryptblock(c, offset);
        }
    };

// ECB never uses an IV
    ECB_prototype.fixOptions = function (options) {
        options.iv = [];
    };


    /**
     * Cipher block chaining
     *
     * The first block is XORed with the IV. Subsequent blocks are XOR with the
     * previous cipher output.
     */
    var CBC = C_mode.CBC = function () {
        // Call parent constructor
        Mode.apply(this, arguments);
    };

// Inherit from Mode
    var CBC_prototype = CBC.prototype = new Mode;

// Concrete steps for Mode template
    CBC_prototype._doEncrypt = function (cipher, m, iv) {
        var blockSizeInBytes = cipher._blocksize * 4;

        // Encrypt each block
        for (var offset = 0; offset < m.length; offset += blockSizeInBytes) {
            if (offset == 0) {
                // XOR first block using IV
                for (var i = 0; i < blockSizeInBytes; i++)
                    m[i] ^= iv[i];
            } else {
                // XOR this block using previous crypted block
                for (var i = 0; i < blockSizeInBytes; i++)
                    m[offset + i] ^= m[offset + i - blockSizeInBytes];
            }
            // Encrypt block
            cipher._encryptblock(m, offset);
        }
    };
    CBC_prototype._doDecrypt = function (cipher, c, iv) {
        var blockSizeInBytes = cipher._blocksize * 4;

        // At the start, the previously crypted block is the IV
        var prevCryptedBlock = iv;

        // Decrypt each block
        for (var offset = 0; offset < c.length; offset += blockSizeInBytes) {
            // Save this crypted block
            var thisCryptedBlock = c.slice(offset, offset + blockSizeInBytes);
            // Decrypt block
            cipher._decryptblock(c, offset);
            // XOR decrypted block using previous crypted block
            for (var i = 0; i < blockSizeInBytes; i++) {
                c[offset + i] ^= prevCryptedBlock[i];
            }
            prevCryptedBlock = thisCryptedBlock;
        }
    };


    /**
     * Cipher feed back
     *
     * The cipher output is XORed with the plain text to produce the cipher output,
     * which is then fed back into the cipher to produce a bit pattern to XOR the
     * next block with.
     *
     * This is a stream cipher mode and does not require padding.
     */
    var CFB = C_mode.CFB = function () {
        // Call parent constructor
        Mode.apply(this, arguments);
    };

// Inherit from Mode
    var CFB_prototype = CFB.prototype = new Mode;

// Override padding
    CFB_prototype._padding = C_pad.NoPadding;

// Concrete steps for Mode template
    CFB_prototype._doEncrypt = function (cipher, m, iv) {
        var blockSizeInBytes = cipher._blocksize * 4,
            keystream = iv.slice(0);

        // Encrypt each byte
        for (var i = 0; i < m.length; i++) {

            var j = i % blockSizeInBytes;
            if (j == 0) cipher._encryptblock(keystream, 0);

            m[i] ^= keystream[j];
            keystream[j] = m[i];
        }
    };
    CFB_prototype._doDecrypt = function (cipher, c, iv) {
        var blockSizeInBytes = cipher._blocksize * 4,
            keystream = iv.slice(0);

        // Encrypt each byte
        for (var i = 0; i < c.length; i++) {

            var j = i % blockSizeInBytes;
            if (j == 0) cipher._encryptblock(keystream, 0);

            var b = c[i];
            c[i] ^= keystream[j];
            keystream[j] = b;
        }
    };


    /**
     * Output feed back
     *
     * The cipher repeatedly encrypts its own output. The output is XORed with the
     * plain text to produce the cipher text.
     *
     * This is a stream cipher mode and does not require padding.
     */
    var OFB = C_mode.OFB = function () {
        // Call parent constructor
        Mode.apply(this, arguments);
    };

// Inherit from Mode
    var OFB_prototype = OFB.prototype = new Mode;

// Override padding
    OFB_prototype._padding = C_pad.NoPadding;

// Concrete steps for Mode template
    OFB_prototype._doEncrypt = function (cipher, m, iv) {

        var blockSizeInBytes = cipher._blocksize * 4,
            keystream = iv.slice(0);

        // Encrypt each byte
        for (var i = 0; i < m.length; i++) {

            // Generate keystream
            if (i % blockSizeInBytes == 0)
                cipher._encryptblock(keystream, 0);

            // Encrypt byte
            m[i] ^= keystream[i % blockSizeInBytes];

        }
    };
    OFB_prototype._doDecrypt = OFB_prototype._doEncrypt;

    /**
     * Counter
     * @author Gergely Risko
     *
     * After every block the last 4 bytes of the IV is increased by one
     * with carry and that IV is used for the next block.
     *
     * This is a stream cipher mode and does not require padding.
     */
    var CTR = C_mode.CTR = function () {
        // Call parent constructor
        Mode.apply(this, arguments);
    };

// Inherit from Mode
    var CTR_prototype = CTR.prototype = new Mode;

// Override padding
    CTR_prototype._padding = C_pad.NoPadding;

    CTR_prototype._doEncrypt = function (cipher, m, iv) {
        var blockSizeInBytes = cipher._blocksize * 4;
        var counter = iv.slice(0);

        for (var i = 0; i < m.length;) {
            // do not lose iv
            var keystream = counter.slice(0);

            // Generate keystream for next block
            cipher._encryptblock(keystream, 0);

            // XOR keystream with block
            for (var j = 0; i < m.length && j < blockSizeInBytes; j++, i++) {
                m[i] ^= keystream[j];
            }

            // Increase counter
            if (++(counter[blockSizeInBytes - 1]) == 256) {
                counter[blockSizeInBytes - 1] = 0;
                if (++(counter[blockSizeInBytes - 2]) == 256) {
                    counter[blockSizeInBytes - 2] = 0;
                    if (++(counter[blockSizeInBytes - 3]) == 256) {
                        counter[blockSizeInBytes - 3] = 0;
                        ++(counter[blockSizeInBytes - 4]);
                    }
                }
            }
        }
    };
    CTR_prototype._doDecrypt = CTR_prototype._doEncrypt;

})();
